/*
 * PropertyChangeObject.java
 *
 * Created on 4 November 2003, 12:36
 *
 * Copyright 2008 the Desert Labs Project (http://desertlabs.us)
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package us.desertlabs.beans;

import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;

/**
 * This is a convenient base class for the creation of new Java Beans (and other
 * classes) that want to support <CODE>PropertyChangeEvent</CODE> dispatching
 * and listener management. In addition to handling the <CODE>PropertyChangeSupport</CODE>
 * object and the calls that must be delegated to it, this class adds some new
 * methods to allow easier cleanup of existing listeners.
 * <P>
 * External code can use the provided methods directly to add and remove
 * listeners. However, if a subclass wishes to fire an event, it should call the
 * <CODE>getPropertyChangeSupport()</CODE> method and use the event firing
 * methods provided by that instance.
 *
 * @author Brandon Franklin
 */
public abstract class PropertyChangeObject {

    /** The set of properties that have listeners attached */
    private final Set<String> propertiesWithListeners;

    /** The property change manager for this instance */
    private final PropertyChangeSupport propertyChangeSupport;

    /**
     * Creates a new instance of <CODE>PropertyChangeObject</CODE>.
     */
    protected PropertyChangeObject() {
        propertyChangeSupport = new PropertyChangeSupport( this );
        propertiesWithListeners = Collections.synchronizedSet( new HashSet<String>() );
    }

    /**
     * Adds a <CODE>PropertyChangeListener</CODE> to the listener list. The
     * listener is registered for all properties.
     *
     * @param listener the PropertyChangeListener to be added
     */
    public final void addPropertyChangeListener( final PropertyChangeListener listener ) {
        getPropertyChangeSupport().addPropertyChangeListener( listener );
    }

    /**
     * Adds a <CODE>PropertyChangeListener</CODE> for a specific property.
     *
     * @param propertyName the name of the property to listen to
     * @param listener the <CODE>PropertyChangeListener</CODE> to be added
     */
    public final void addPropertyChangeListener( final String propertyName, final PropertyChangeListener listener ) {
        getPropertyChangeSupport().addPropertyChangeListener( propertyName, listener );
        propertiesWithListeners.add( propertyName );
    }

    /**
     * Returns an array of all the listeners that were added to the object with
     * <CODE>addPropertyChangeListener()</CODE>.
     *
     * @return all of the <code>PropertyChangeListeners</code> added or an
     *         empty array if no listeners have been added
     */
    public final PropertyChangeListener[] getPropertyChangeListeners() {
        return getPropertyChangeSupport().getPropertyChangeListeners();
    }

    /**
     * Returns an array of all the listeners which have been associated with the
     * named property.
     *
     * @param propertyName the name of the property to return the listeners for
     * @return all of the <CODE>PropertyChangeListeners</CODE> associated with
     *         the named property or an empty array if no listeners have been
     *         added
     */
    public final PropertyChangeListener[] getPropertyChangeListeners( final String propertyName ) {
        return getPropertyChangeSupport().getPropertyChangeListeners( propertyName );
    }

    /**
     * Check if there are any listeners for a specific property.
     *
     * @param propertyName the property name
     * @return true if there are ore or more listeners for the given property
     */
    public final boolean hasListeners( final String propertyName ) {
        return getPropertyChangeSupport().hasListeners( propertyName );
    }

    /**
     * Removes any and all <CODE>PropertyChangeListeners</CODE> that are
     * listening to this object in any context.
     */
    public final void removeAllPropertyChangeListeners() {
        final PropertyChangeListener[] listeners = getPropertyChangeSupport().getPropertyChangeListeners();
        for( final PropertyChangeListener listener : listeners ) {
            getPropertyChangeSupport().removePropertyChangeListener( listener );
        }

        String[] propNames = new String[propertiesWithListeners.size()];
        propNames = propertiesWithListeners.toArray( propNames );
        for( final String propName : propNames ) {
            removeAllPropertyChangeListeners( propName );
        }
    }

    /**
     * Removes any and all <CODE>PropertyChangeListeners</CODE> that are
     * listening to a specific property.
     *
     * @param propertyName the property name
     */
    public final void removeAllPropertyChangeListeners( final String propertyName ) {
        final PropertyChangeListener[] listeners = getPropertyChangeSupport().getPropertyChangeListeners( propertyName );
        for( final PropertyChangeListener listener : listeners ) {
            getPropertyChangeSupport().removePropertyChangeListener( propertyName, listener );
        }
        propertiesWithListeners.remove( propertyName );
    }

    /**
     * Removes a <CODE>PropertyChangeListener</CODE> from the listener list.
     * This removes a listener that was registered for all properties.
     *
     * @param listener the PropertyChangeListener to be removed
     */
    public final void removePropertyChangeListener( final PropertyChangeListener listener ) {
        getPropertyChangeSupport().removePropertyChangeListener( listener );
    }

    /**
     * Remove a <CODE>PropertyChangeListener</CODE> for a specific property.
     *
     * @param propertyName the name of the property that was listened to
     * @param listener the <CODE>PropertyChangeListener</CODE> to be removed
     */
    public final void removePropertyChangeListener( final String propertyName, final PropertyChangeListener listener ) {
        getPropertyChangeSupport().removePropertyChangeListener( propertyName, listener );
        if( !getPropertyChangeSupport().hasListeners( propertyName ) ) {
            propertiesWithListeners.remove( propertyName );
        }
    }

    /**
     * Returns the <CODE>PropertyChangeSupport</CODE> instance being used by
     * this object instance. Subclasses should use this method to obtain the
     * instance for firing new events.
     *
     * @return the <CODE>PropertyChangeSupport</CODE> instance being used by
     *         this object instance
     */
    protected final PropertyChangeSupport getPropertyChangeSupport() {
        return propertyChangeSupport;
    }
}
